package com.cardone.common.template.support;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;

import com.cardone.common.cache.Caches;
import com.cardone.common.cache.util.CacheUtils;
import com.cardone.context.DictionaryException;
import com.google.common.collect.Lists;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
 * FreeMarkerTemplate支持
 *
 * @author yaohaitao
 */
@Getter
@Setter
@Accessors(chain = true)
@Slf4j
public class TemplateFreeMarkerSupport implements TemplateSupport {
	/**
	 * 缓存解析模板
	 */
	private Boolean cacheProcess = true;

	/**
	 * freemarker.template.Configuratio
	 */
	private Configuration configuration;

	/**
	 * 文件名解析格式
	 */
	private String filePathFormate = "${filePath}.ftl";

	/**
	 * 文件名变量名称
	 */
	private String filePathVariableName = "${filePath}";

	/**
	 * 显示model
	 */
	private Boolean showModel;

	/**
	 * 显示解析模板后字符串
	 */
	private Boolean showProcessTemplateIntoString;

	/**
	 * 显示模板字符串
	 */
	private Boolean showTemplateString;

	/**
	 * 获取：新文件路径
	 *
	 * @param filePath
	 *          文件路径
	 * @param isFormat
	 *          是否格式化文件路径
	 * @return 新文件路径
	 */
	private String getNewFilePath(final String filePath, final boolean isFormat) {
		if (!isFormat) {
			return filePath;
		}

		return StringUtils.replace(this.filePathFormate, this.filePathVariableName, filePath);
	}

	private void logModel(final Object model) {
		if (BooleanUtils.isTrue(this.showModel)) {
			TemplateFreeMarkerSupport.log.warn(Objects.toString(model));
		}
	}

	private void logStr(final String str) {
		if (BooleanUtils.isTrue(this.showProcessTemplateIntoString)) {
			TemplateFreeMarkerSupport.log.warn(str);
		}
	}

	private void logTemplate(final Template template) {
		if (BooleanUtils.isTrue(this.showTemplateString)) {
			TemplateFreeMarkerSupport.log.warn(template.toString());
		}
	}

	@Override
	public String process(final String filePath, final boolean isFormat, final Object model) {
		final List<Object> processKeyList = Lists.newArrayList(filePath, isFormat, model);

		String str = null;

		if (BooleanUtils.isTrue(this.cacheProcess)) {
			str = CacheUtils.getCacheValue(String.class, TemplateSupport.class.getName(), processKeyList);

			if (StringUtils.isNotBlank(str)) {
				return str;
			}
		}

		final String newFilePath = this.getNewFilePath(filePath, isFormat);

		try {
			this.logModel(model);

			final Template template = this.configuration.getTemplate(newFilePath);

			this.logTemplate(template);

			str = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);

			this.logStr(str);

			if (BooleanUtils.isTrue(this.cacheProcess) && StringUtils.equals(template.toString(), str)) {
				CacheUtils.getCache(TemplateSupport.class.getName()).put(processKeyList, str);
			}

			return str;
		} catch (final IOException e) {
			throw new DictionaryException(e);
		} catch (final TemplateException e) {
			throw new DictionaryException(e);
		}
	}

	@Override
	public String process(final String filePath, final Object model) {
		return this.process(filePath, true, model);
	}

	@Override
	@Cacheable(value = TemplateSupport.BEAN_ID, key = Caches.KEY_2)
	public String processString(final String templateString, final Object model) {
		if (StringUtils.isBlank(templateString)) {
			return null;
		}

		StringWriter writer = null;

		Reader reader = null;

		try {
			writer = new StringWriter();

			final String name = UUID.randomUUID().toString();

			reader = new StringReader(templateString);

			final Template template = new Template(name, reader, this.configuration);

			template.process(model, writer);

			return writer.toString();
		} catch (final Exception e) {
			throw new RuntimeException(e);
		} finally {
			IOUtils.closeQuietly(reader);

			IOUtils.closeQuietly(writer);
		}
	}
}
